---
title: Insert, update, or delete a stored preference set
summary: SavePreferences end-to-end process on SAIF.Services.WCF.Services.PreferenceService.
status: draft
owner: SAIF Doc Gen
last_reviewed: 2026-09-22
operation_id: OP-007
trigger: Caller invokes `SavePreferences` over the `PreferenceService` route.
---

# OP-007: Insert, update, or delete a stored preference set

SavePreferences is invoked over the `PreferenceService` route on `SAIF.Services.WCF.Services.PreferenceService`; this page documents the process end to end, from route dispatch to the message the caller receives.

| Field | Value |
| --- | --- |
| **Operation ID** | OP-007 |
| **Trigger** | Caller invokes `SavePreferences` over the `PreferenceService` route. |
| **Entry operation** | [`SavePreferences`](../contracts/preference-service.md#savepreferences) |
| **Owning component** | `SAIF.Services.WCF.Services.PreferenceService.SavePreferences` |
| **Inputs** | Request message parts listed below |
| **Outputs** | No response payload (void) |
| **Failure behavior** | Generic SOAP fault, per [Faults and error pipeline](../faults.md) |

## Request and response

| Direction | Element | Type | Declared in | Notes |
| --- | --- | --- | --- | --- |
| Request | `applicationName` | `string` | BCL (`System`) | Application name portion of the preference key; lower-cased before matching. (Yes) |
| Request | `pageName` | `string` | BCL (`System`) | Page name portion of the preference key; a blank value is normalized to the empty string. (No (default `None`)) |
| Request | `user` | `string` | BCL (`System`) | User whose preferences are written; lower-cased before matching. (Yes) |
| Request | `preferences` | `IDictionary<string, string>` | BCL (`System`) | The preference set to persist; an empty dictionary triggers deletion of an existing record. (Yes) |
| Request | `currentUser` | `string` | BCL (`System`) | Unverified caller-supplied audit value; presence-checked but not authenticated. (Yes) |
| Response | `void` | `void` | n/a (void) | Not applicable. |

<!-- Types declared in `SAIF.Services.WCF.Contracts` are attributed to `SAIF.Framework.Core`, inferred from source and package references and marked `*`; their members are not enumerated because the external declaration was not read, marked `**`. -->

## Steps

1. `Enforce.ArgumentNotNullOrEmpty(currentUser, "currentUser")` runs before any database work ([BR-WCF-007](../business-rules.md#br-wcf-007-savepreferences-requires-a-current-user)).
   - On failure: `ArgumentNullException`, logged and surfaced as a generic SOAP fault.
2. `unitOfWork.Begin()` opens the transaction boundary that covers the remaining steps.
3. Lower-case `currentUser` into the audit field and re-run `QueryPrefs` to locate any existing record ([BR-WCF-005](../business-rules.md#br-wcf-005-preference-keys-are-case-normalized), [BR-WCF-011](../business-rules.md#br-wcf-011-persisted-changes-stamp-a-local-timestamp)).
4. No existing record and a non-empty `preferences` dictionary: `AddPreference` builds a `Preference` (storing a blank page name as null) and calls `repository.Add` ([BR-WCF-008](../business-rules.md#br-wcf-008-new-preference-set-is-inserted)).
5. Existing record and an empty `preferences` dictionary: `DeletePreference` calls `repository.Delete` ([BR-WCF-009](../business-rules.md#br-wcf-009-emptied-preference-set-is-deleted)).
6. Existing record and a non-empty dictionary: `UpdatePreference` repopulates the tracked entity in place ([BR-WCF-010](../business-rules.md#br-wcf-010-existing-preference-set-is-updated-in-place)); `PopulatePreference` serializes the dictionary to JSON and stamps `UpdateDate = DateTime.Now` and `UpdateUser = currentUser` ([BR-WCF-011](../business-rules.md#br-wcf-011-persisted-changes-stamp-a-local-timestamp)).
7. `unitOfWork.Commit()`.
   - On failure before commit: logged by the installed error handler, surfaced as a generic SOAP fault, and the transaction is rolled back when `Dispose` runs.

## Process flow

The following diagram visualizes this process's control flow.

```mermaid
flowchart TD
    A["Validate currentUser is present"]
    B["Begin transaction"]
    C["Normalize currentUser and<br>locate existing record"]
    C1{"Existing record<br>and preferences dictionary"}
    D["Insert new preference record"]
    E["Delete existing preference record"]
    F["Update existing preference record"]
    G["unitOfWork.Commit()."]
    A --> B
    B --> C
    C --> C1
    C1 -->|"No existing record, non-empty"| D
    C1 -->|"Existing record, empty"| E
    C1 -->|"Existing record, non-empty"| F
    D --> G
    E --> G
    F --> G
```

## Transaction and side effects

| Aspect | Finding |
| --- | --- |
| Unit of work | `OracleUnitOfWorkFactory` (`CLM`) |
| Commit point | `unitOfWork.Commit()` after the add/delete/update decision |
| Records written | `Preference` (insert, update, or delete) |
| Downstream calls | None |
| Shared state touched | None |
| Repeat-call behavior | Re-running with the same arguments reaches the same terminal state (upsert-like) |

## Logging

| Surface | Written content |
| --- | --- |
| `ErrorLogErrorHandler` → `LogService` | Failure-path exception logging only, carrying method signature, calling user and exception detail; see [Faults and error pipeline](../faults.md) |

## Faults and error handling

| Failure | Fault type the caller receives | Produced by | Source |
| --- | --- | --- | --- |
| `ArgumentNullException` (missing `currentUser`) | `Generic SOAP fault` | The operation itself, via `Enforce` | src/WCF/SAIF.Services.WCF.Host/Services/PreferenceService.cs |
| Oracle/NHibernate exception during commit | `Generic SOAP fault` | The `ErrorLogErrorHandler` installed by `[LogErrorBehavior]` | src/WCF/SAIF.Services.WCF.Host/Services/PreferenceService.cs |

## Authorization

| Requirement | Enforced by | Source | Applies to |
| --- | --- | --- | --- |
| [None in code](../authentication-authorization.md#authorization-enforcement) | IIS authentication on the deployment (see [Activation and hosting](../activation-hosting.md)) | `src/WCF/SAIF.Services.WCF.Host/Services/PreferenceService.cs` | This operation |

## Related documentation
- [Index](../index.md)
- [Overview](../overview.md)
- [Architecture](../architecture.md)
- [Activation and hosting](../activation-hosting.md)
- [Operations](../operations.md)
- [Faults and error pipeline](../faults.md)
- [Authentication and authorization](../authentication-authorization.md)
- [Dependencies](../dependencies.md)
- [Configuration](../configuration.md)
- [Operations and processes](../operations-processes.md)
- [Business rules](../business-rules.md)
- [Data model](../data-model.md)
- [Operational runbook](../operational-runbook.md)
- [Tech debt](../tech-debt.md)

> **Note:** Statements marked with `*` were inferred from code structure, naming
> conventions, or contextual evidence where direct confirmation was not available
> in source artifacts. Statements marked with `**` could not be confirmed; treat
> as unknown until verified.

## Sources
- src/WCF/SAIF.Services.WCF.Host/Services/PreferenceService.cs
